package jp.co.recruit_lifestyle.ohos.widget;

import jp.co.recruit_lifestyle.ohos.widget.util.LogUtil;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

public class WaveSwipeRefreshLayout extends ComponentContainer implements ComponentTreeObserver.WindowBoundListener, Component.EstimateSizeListener, ComponentContainer.ArrangeListener, Component.TouchEventListener {


    @Override
    public void onWindowUnbound() {

    }

    private enum VERTICAL_DRAG_THRESHOLD {
        FIRST(0.1f), SECOND(0.16f + FIRST.val), THIRD(0.5f + FIRST.val);

        final float val;

        VERTICAL_DRAG_THRESHOLD(float val) {
            this.val = val;
        }
    }

    private enum STATE {
        REFRESHING, PENDING;
    }

    private enum EVENT_PHASE {
        WAITING, BEGINNING, APPEARING, EXPANDING, DROPPING;
    }


    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;

    private static final int INVALID_POINTER = -1;

    private static final float DRAGGING_WEIGHT = 0.6f;

    /**
     * 落ちる前の回転の最大のAngle値
     */
    private static final float MAX_PROGRESS_ROTATION_RATE = 0.8f;

    /**
     * {@link WaveSwipeRefreshLayout#mCircleView} が消えるときのDuration
     */
    private static final int SCALE_DOWN_DURATION = 200;

    /**
     * {@link WaveSwipeRefreshLayout.ProgressAnimationImageView#mProgress}
     * の回転が始まるまでのDuration
     */
    private static final int ANIMATE_TO_TRIGGER_DURATION = 200;

    /**
     * デフォルトのCircleのTargetの値
     */
    private static final int DEFAULT_CIRCLE_TARGET = 64;

    private Component mTarget;//内部ListContainer等可滑动控件

    /**
     * Refreshを通知するListener
     */
    private OnRefreshListener mListener;

    /**
     * リフレッシュ状態
     */
    private STATE mState = STATE.PENDING;

    private EVENT_PHASE mEventPhase = EVENT_PHASE.WAITING;

    private ProgressAnimationImageView mCircleView;

    /**
     * 波のView
     */
    private WaveView mWaveView;


    /**
     * {@link WaveSwipeRefreshLayout#mListener} に通知するかどうかのFlag
     */
    private boolean mNotify;

    private boolean mIsManualRefresh = false;

    private float mFirstTouchDownPointY;

    private boolean mIsBeingDropped;

    private int mActivePointerId = INVALID_POINTER;

    private int mTopOffset;

    public WaveSwipeRefreshLayout(Context context) {
        this(context, null);
    }

    public WaveSwipeRefreshLayout(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public WaveSwipeRefreshLayout(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);

        setEstimateSizeListener(this);
        setTouchEventListener(this);
        setArrangeListener(this);
        getComponentTreeObserver().addWindowBoundListener(this);

        createProgressView();
        createWaveView();
    }

    private void createProgressView() {
        addComponent(mCircleView = new ProgressAnimationImageView(getContext()));
    }

    private void createWaveView() {
        mWaveView = new WaveView(getContext());
        addComponent(mWaveView, 0);
    }


    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        ensureTarget();


        int w = EstimateSpec.getSize(widthMeasureSpec);
        int h = EstimateSpec.getSize(heightMeasureSpec);

        setEstimatedSize(
                makeMeasureSpecExactly(w),
                makeMeasureSpecExactly(h));

        //// Propagates measuring to child view and to circle view

        // Measures on child view with each directed length without padding
        mTarget.estimateSize(
                makeMeasureSpecExactly(w - (getPaddingLeft() + getPaddingRight())),
                makeMeasureSpecExactly(h - (getPaddingTop() + getPaddingBottom())));
        mWaveView.estimateSize(widthMeasureSpec, heightMeasureSpec);

        mCircleView.onEstimateSize(widthMeasureSpec, heightMeasureSpec);
        return true;
    }

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        if (getChildCount() == 0) {
            return false;
        }

        ensureTarget();

        final int thisWidth = getEstimatedWidth();
        final int thisHeight = getEstimatedHeight();

        final int childRight = thisWidth - getPaddingRight();
        final int childBottom = thisHeight - getPaddingBottom();
        mTarget.arrange(getPaddingLeft(), getPaddingTop(), childRight, childBottom);

        layoutWaveView();
        return true;
    }

    private void layoutWaveView() {
        if (mWaveView == null) {
            return;
        }
        final int thisWidth = getEstimatedWidth();
        final int thisHeight = getEstimatedHeight();

        final int circleWidth = mCircleView.getEstimatedWidth();
        final int circleHeight = mCircleView.getEstimatedHeight();
        mCircleView.arrange((thisWidth - circleWidth) / 2, -circleHeight + mTopOffset,
                circleWidth, circleHeight);
        final int childRight = thisWidth - getPaddingRight();
        final int childBottom = thisHeight - getPaddingBottom();
        mWaveView.arrange(getPaddingLeft(), mTopOffset + getPaddingTop(), childRight, childBottom);
    }

    public void setTopOffsetOfWave(int topOffset) {
        if (topOffset < 0) {
            return;
        }
        mTopOffset = topOffset;
        layoutWaveView();
    }


    @Override
    public void onWindowBound() {
        getComponentTreeObserver().removeWindowBoundListener(this);
        moveChildToFront(mWaveView);
        moveChildToFront(mCircleView);
        if (mIsManualRefresh) {
            mIsManualRefresh = false;
            mWaveView.manualRefresh();
            reInitCircleView();
            ShapeElement element = new ShapeElement();
            element.setRgbColor(new RgbColor(Color.TRANSPARENT.getValue()));
            mCircleView.setBackground(element);
            mCircleView.setTranslationY(
                    mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2);
            animateOffsetToCorrectPosition();
        }
    }

    /**
     * Make circle view be visible and even scale.
     */
    private void reInitCircleView() {
        if (mCircleView.getVisibility() != Component.VISIBLE) {
            mCircleView.setVisibility(Component.VISIBLE);
        }

        mCircleView.scaleWithKeepingAspectRatio(1f);
        mCircleView.makeProgressTransparent();
    }


    private boolean onMoveTouchEvent(TouchEvent event, float diffY) {
        if (mIsBeingDropped) {
            return false;
        }

        final float overScrollTop = diffY * DRAGGING_WEIGHT; //调整拖动权重
//        final float overScrollTop = diffY;

        LogUtil.debug("overscroll", "overScrollTop:" + overScrollTop + "  diffY:" + diffY);
        if (overScrollTop < 0) {
            mCircleView.showArrow(false);
            return false;
        }

        final float density = DisplayManager.getInstance().getDefaultDisplay(this.getContext()).get().getAttributes().densityPixels;

        float originalDragPercent = overScrollTop / (DEFAULT_CIRCLE_TARGET * density);
        float dragPercent = Math.min(1f, originalDragPercent);
        float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;

        // 0f...2f
        float tensionSlingshotPercent =
                (originalDragPercent > 3f) ? 2f : (originalDragPercent > 1f) ? originalDragPercent - 1f : 0;
        float tensionPercent = (4f - tensionSlingshotPercent) * tensionSlingshotPercent / 8f;

        mCircleView.showArrow(true);
        reInitCircleView();

        if (originalDragPercent < 1f) {
            float strokeStart = adjustedPercent * .8f;
            mCircleView.setProgressStartEndTrim(0f, Math.min(MAX_PROGRESS_ROTATION_RATE, strokeStart));
            mCircleView.setArrowScale(Math.min(1f, adjustedPercent));
        }

        float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
        mCircleView.setProgressRotation(rotation);
        mCircleView.setTranslationY(mWaveView.getCurrentCircleCenterY());

        float seed = diffY / Math.min(getEstimatedWidth(), getEstimatedHeight());
        float firstBounds = seed * (5f - 2 * seed) / 3.5f;
        float secondBounds = firstBounds - VERTICAL_DRAG_THRESHOLD.FIRST.val;
        float finalBounds = (firstBounds - VERTICAL_DRAG_THRESHOLD.SECOND.val) / 5;

        if (firstBounds < VERTICAL_DRAG_THRESHOLD.FIRST.val) {
            // draw a wave and not draw a circle
            onBeginPhase(firstBounds);
            LogUtil.debug("proms", "FIRST" + firstBounds);
        } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.SECOND.val) {
            // draw a circle with a wave
            onAppearPhase(firstBounds, secondBounds);
            LogUtil.debug("proms", "SECOND" + firstBounds);
        } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.THIRD.val) {
            // draw a circle with expanding a wave
            onExpandPhase(firstBounds, secondBounds, finalBounds);
            LogUtil.debug("proms", "THIRD" + firstBounds + "  mEventPhase" + mEventPhase);
        } else {
            // stop to draw a wave and drop a circle
            onDropPhase();
            LogUtil.debug("proms", "onDropPhase" + firstBounds);
        }

        return !mIsBeingDropped;
    }

    private void onBeginPhase(float move1) {
        //最初の小波の描画
        mWaveView.beginPhase(move1);

        setEventPhase(EVENT_PHASE.BEGINNING);
    }

    private void onAppearPhase(float move1, float move2) {
        //すでに描画されている波に対して追加で円を描画する
        mWaveView.appearPhase(move1, move2);

        setEventPhase(EVENT_PHASE.APPEARING);
    }

    private void onExpandPhase(float move1, float move2, float move3) {
        mWaveView.expandPhase(move1, move2, move3);

        setEventPhase(EVENT_PHASE.EXPANDING);
    }

    private void onDropPhase() {
        //水滴下落动画
        mWaveView.animationDropCircle();

        //控制箭头下落
        ValueAnimator animator = ValueAnimator.ofFloat(0, 0);
        animator.setDuration(500);
        animator.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mCircleView.setTranslationY(
                        mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2.f);
            }
        });
        animator.start();

        setRefreshing(true, true);
        mIsBeingDropped = true;
        setEventPhase(EVENT_PHASE.DROPPING);
        setEnabled(false);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        onComponentTouchEvent(touchEvent);
        return true;
    }


    public void onComponentTouchEvent(TouchEvent touchEvent) {
        ensureTarget();
        if (!isEnabled() || canChildScrollUp() || isRefreshing()) {
            return;
        }

        int action = touchEvent.getAction();

        mIsBeingDropped = mWaveView.isDisappearCircleAnimatorRunning();

        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mActivePointerId = touchEvent.getPointerId(0);
                mFirstTouchDownPointY = touchEvent.getPointerScreenPosition(0).getY();
                break;

            case TouchEvent.POINT_MOVE:
                if (mActivePointerId == INVALID_POINTER) {
                    return;
                }
                final float currentY = touchEvent.getPointerScreenPosition(0).getY();

                if (currentY == -1) {
                    return;
                }

                if (mFirstTouchDownPointY == -1) {
                    mFirstTouchDownPointY = currentY;
                }

                final float yDiff = currentY - mFirstTouchDownPointY;

                LogUtil.debug("ydiff", "yDiff" + yDiff + "  currentY:" + currentY + "- mFirstTouchDownPointY:" + mFirstTouchDownPointY);
                // State is changed to drag if over slop
                if (yDiff > 20 && !isRefreshing()) {
                    mTarget.setEnabled(false);
                    mCircleView.makeProgressTransparent();
                    onMoveTouchEvent(touchEvent, yDiff);
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                mTarget.setEnabled(true);
                mActivePointerId = INVALID_POINTER;

                if (mIsBeingDropped) {
                    mIsBeingDropped = false;
                    return;
                }

                final float diffY = touchEvent.getPointerScreenPosition(0).getY() - mFirstTouchDownPointY;
                final float waveHeightThreshold =
                        diffY * (5f - 2 * diffY / Math.min(getEstimatedWidth(), getEstimatedHeight())) / 1000f;
                mWaveView.startWaveAnimation(waveHeightThreshold);

                if (!isRefreshing()) {
                    mCircleView.setProgressStartEndTrim(0f, 0f);
                    mCircleView.showArrow(false);
                    mCircleView.setVisibility(Component.HIDE);
                }
                mActivePointerId = INVALID_POINTER;
                break;
        }
    }

    private float getMotionEventY(TouchEvent ev, int activePointerId) {
        final int index = ev.getIndex();
        if (index < 0) {
            return -1;
        }
        return ev.getPointerPosition(activePointerId).getY();
    }

    //控制箭头旋转动画
    private void animateOffsetToCorrectPosition() {
        mAnimateToCorrectPosition.stop();
        mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
        mAnimateToCorrectPosition.setCurveType(Animator.CurveType.DECELERATE);
        mAnimateToCorrectPosition.setStateChangedListener(mRefreshListener);
        mAnimateToCorrectPosition.start();
    }

    private AnimatorValue mAnimateToCorrectPosition = new AnimatorValue();


    /**
     * @param dropHeight 高さ
     */
    public void setMaxDropHeight(int dropHeight) {
        mWaveView.setMaxDropHeight(dropHeight);
    }

    private Animator.StateChangedListener mRefreshListener = new AnimatorValue.StateChangedListener() {

        @Override
        public void onStart(Animator animator) {

        }

        @Override
        public void onStop(Animator animator) {

        }

        @Override
        public void onCancel(Animator animator) {

        }

        @Override
        public void onEnd(Animator animator) {
            if (isRefreshing()) {
                mCircleView.makeProgressTransparent();
                mCircleView.startProgress();
                if (mNotify) {
                    if (mListener != null) {
                        mListener.onRefresh();
                    }
                }
            } else {
                mCircleView.stopProgress();
                mCircleView.setVisibility(Component.HIDE);
                mCircleView.makeProgressTransparent();
                mWaveView.startDisappearCircleAnimation();
            }
        }

        @Override
        public void onPause(Animator animator) {

        }

        @Override
        public void onResume(Animator animator) {

        }
    };

    private void ensureTarget() {
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                Component child = getComponentAt(i);
                if (!child.equals(mCircleView) && !child.equals(mWaveView)) {
                    mTarget = child;
                    break;
                }
            }
        }

        if (mTarget == null) {
            throw new IllegalStateException("This view must have at least one ListContainer");
        }
    }

    /**
     * @param refreshing Refreshの状態
     * @param notify     Listenerに通知するかどうか
     */
    private void setRefreshing(boolean refreshing, final boolean notify) {
        if (isRefreshing() != refreshing) {
            mNotify = notify;
            ensureTarget();
            setState(refreshing);
            if (isRefreshing()) {
                animateOffsetToCorrectPosition();
            } else {
                startScaleDownAnimation(mRefreshListener);
            }
        }
    }


    private void setEventPhase(EVENT_PHASE eventPhase) {
        mEventPhase = eventPhase;
    }

    private void setState(STATE state) {
        mState = state;
        setEnabled(true);
        if (!isRefreshing()) {
            setEventPhase(EVENT_PHASE.WAITING);
        }
    }


    private void setState(boolean doRefresh) {
        setState((doRefresh) ? STATE.REFRESHING : STATE.PENDING);
    }

    //控制箭头消失
    private void startScaleDownAnimation(Animator.StateChangedListener listener) {
        AnimatorValue scaleDownAnimation = new AnimatorValue();
        scaleDownAnimation.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                mCircleView.scaleWithKeepingAspectRatio(1 - v);
            }
        });
        scaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
        scaleDownAnimation.setStateChangedListener(listener);
        scaleDownAnimation.start();//TODO 实现mCircleView.clearAnimation();
    }

    /**
     * @param colorResIds ColorのId達
     */
    public void setColorSchemeResources(int... colorResIds) {
        mCircleView.setProgressColorSchemeColorsFromResource(colorResIds);
    }

    /**
     * @param colors セットするColor達
     */
    public void setColorSchemeColors(int... colors) {
        // FIXME Add @NonNull to the argument
        ensureTarget();
        mCircleView.setProgressColorSchemeColors(colors);
    }

    /**
     * @return {@link WaveSwipeRefreshLayout#mState} == REFRESHING of {@link
     * WaveSwipeRefreshLayout.STATE}
     */
    public boolean isRefreshing() {
        return mState == STATE.REFRESHING;
    }

    private boolean isBeginning() {
        return mEventPhase == EVENT_PHASE.BEGINNING;
    }

    private boolean isExpanding() {
        return mEventPhase == EVENT_PHASE.EXPANDING;
    }

    private boolean isDropping() {
        return mEventPhase == EVENT_PHASE.DROPPING;
    }

    private boolean isAppearing() {
        return mEventPhase == EVENT_PHASE.APPEARING;
    }

    private boolean isWaiting() {
        return mEventPhase == EVENT_PHASE.WAITING;
    }

    /**
     * @param refreshing {@link WaveSwipeRefreshLayout#mState} のセット
     */
    public void setRefreshing(boolean refreshing) {
        if (refreshing && !isRefreshing()) {
            // scale and show
            setState(true);
            mNotify = false;

            mIsManualRefresh = true;
            if (mWaveView.getCurrentCircleCenterY() == 0) {
                return;
            }
            mWaveView.manualRefresh();
            reInitCircleView();
            mCircleView.setTranslationY(
                    mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2);
            animateOffsetToCorrectPosition();
        } else {
            setRefreshing(refreshing, false /* notify */);
        }
    }

    /**
     * @return ScrollUp出来るかどうか
     */
    public boolean canChildScrollUp() {
        if (mTarget == null) {
            return false;
        }
        //TODO 高低版本兼容
        if (mTarget instanceof ListContainer) {
            ListContainer listContainer = (ListContainer) mTarget;
            return listContainer.getChildCount() > 0 && (listContainer.canScroll(DRAG_DOWN));
//            return listContainer.getChildCount() > 0 && (listContainer.getFirstVisibleItemPosition() > 0
//                    || listContainer.getComponentAt(0).getTop() < listContainer.getPaddingTop());
        }
        return mTarget.canScroll(DRAG_DOWN);
    }

    /**
     * @param radius 波の影の深さ
     */
    public void setShadowRadius(int radius) {
        radius = Math.max(0, radius); // set zero if negative
        mWaveView.setShadowRadius(radius);
    }


    /**
     * This is an alias to WaveView#setWaveColor(int)
     *
     * @see WaveView#setWaveColor(int)
     */
    public void setWaveColor(int argbColor) {
        int alpha = 0xFF & (argbColor >> 24);
        int red = 0xFF & (argbColor >> 16);
        int blue = 0xFF & (argbColor >> 0);
        int green = 0xFF & (argbColor >> 8);
        setWaveARGBColor(alpha, red, green, blue);
    }

    /**
     * WaveView is colored by given rgb color + 0xFF000000
     *
     * @param r int [0, 0xFF]
     * @param g int [0, 0xFF]
     * @param b int [0, 0xFF]
     */
    public void setWaveRGBColor(int r, int g, int b) {
        mWaveView.setWaveColor(Color.rgb(r, g, b));
    }

    /**
     * This is an alias to WaveView#setWaveARGBColor(int)
     *
     * @param a int [0, 0xFF]
     * @param r int [0, 0xFF]
     * @param g int [0, 0xFF]
     * @param b int [0, 0xFF]
     * @see WaveView#setWaveARGBColor(int, int, int, int)
     */
    public void setWaveARGBColor(int a, int r, int g, int b) {
        setWaveRGBColor(r, g, b);
        if (a == 0xFF) {
            return;
        }
        mWaveView.setAlpha((float) a / 255f);
    }

    private static int makeMeasureSpecExactly(int length) {
        return Component.EstimateSpec.getChildSizeWithMode(length, length, Component.EstimateSpec.PRECISE);
    }

    /**
     * @param listener {@link WaveSwipeRefreshLayout.OnRefreshListener}
     */
    public void setOnRefreshListener(OnRefreshListener listener) {
        mListener = listener;
    }

    public interface OnRefreshListener {
        void onRefresh();
    }

    private class ProgressAnimationImageView extends ComponentContainer implements EstimateSizeListener {
        private final MaterialProgressDrawable mProgress;

        public ProgressAnimationImageView(Context context) {
            super(context);

            setEstimateSizeListener(this);

            mProgress = new MaterialProgressDrawable(context, WaveSwipeRefreshLayout.this);
            if (DisplayUtil.isOver600dp(getContext())) { // Make the progress be big
                mProgress.updateSizes(MaterialProgressDrawable.ProgressDrawableSize.LARGE);
            }
            initialize();
        }

        private void initialize() {
            removeAllComponents();

            mProgress.setBackgroundColor(Color.TRANSPARENT.getValue());

            addComponent(mProgress);
            setVisibility(HIDE);
        }

        @Override
        public boolean onEstimateSize(int i, int i1) {
            final int circleDiameter = mProgress.getIntrinsicWidth();

            setEstimatedSize(
                    Component.EstimateSpec.getChildSizeWithMode(circleDiameter, circleDiameter, Component.EstimateSpec.UNCONSTRAINT),
                    Component.EstimateSpec.getChildSizeWithMode(circleDiameter, circleDiameter, Component.EstimateSpec.UNCONSTRAINT)
            );

            mProgress.onEstimateSize(i, i1);
            return true;
        }

        public void makeProgressTransparent() {
            mProgress.setAlpha(1);
        }

        public void showArrow(boolean show) {
            mProgress.showArrow(show);
        }

        public void setArrowScale(float scale) {
            mProgress.setArrowScale(scale);
        }

        /**
         * @param alpha value 0 - 1
         */
        public void setProgressAlpha(float alpha) {
            mProgress.setAlpha(alpha);
        }

        public void setProgressStartEndTrim(float startAngle, float endAngle) {
            mProgress.setStartEndTrim(startAngle, endAngle);
        }

        public void setProgressRotation(float rotation) {
            mProgress.setProgressRotation(rotation);
        }

        public void startProgress() {
            mProgress.start();
        }

        public void stopProgress() {
            mProgress.stop();
        }

        public void setProgressColorSchemeColors(int... colors) {
            mProgress.setColorSchemeColors(colors);
        }

        public void setProgressColorSchemeColorsFromResource(int... resources) {
            final ResourceManager res = getResourceManager();
            final int[] colorRes = new int[resources.length];

            for (int i = 0; i < resources.length; i++) {
                try {
                    colorRes[i] = res.getElement(resources[i]).getColor();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NotExistException e) {
                    e.printStackTrace();
                } catch (WrongTypeException e) {
                    e.printStackTrace();
                }
            }

            setColorSchemeColors(colorRes);
        }

        public void scaleWithKeepingAspectRatio(float scale) {
            setScaleX(scale);
            setScaleY(scale);
        }

    }
}
